home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Ham Radio 2000 #2
/
Ham Radio 2000 - Volume 2.iso
/
HAMV2
/
TCP_IP
/
TNOS230S
/
FTPSERV.C
< prev
next >
Wrap
C/C++ Source or Header
|
1997-09-14
|
39KB
|
1,470 lines
/* Internet FTP Server server machine - see RFC 959
* Copyright 1991 Phil Karn, KA9Q
*
* Mods by KO4KS
*/
#include "global.h"
#include "ctype.h"
#include "commands.h"
#ifdef UNIX
#include <time.h>
#include <sys/stat.h>
#include <fcntl.h>
#endif
#include "mbuf.h"
#include "socket.h"
#include "ftp.h"
#include "ftpserv.h"
#include "dirutil.h"
#include "files.h"
#include "session.h"
#include "smtp.h"
#ifdef CALLSERVER
/* CD-ROM code by Fred Peachman KB7YW */
extern char *CDROM; /* buckbook.c: defines CDROM drive letter e.g. "s:" */
#endif /* #ifdef CALLSERVER */
#ifdef LZW
#include "lzw.h"
#endif
#include "x.h"
#if !defined(_lint)
static char rcsid[] OPTIONAL = "$Id: ftpserv.c,v 1.31 1997/09/14 14:37:46 root Exp root $";
#endif
#ifdef UNIX
int ftpsecuritycheck (char *filename, int perms, int mode);
extern long ACCESSgid, ACCESSuid;
extern long CREATEgid, CREATEuid;
extern short CREATEmask;
extern int CREATEsecure;
#ifndef R_OK
#define R_OK 4
#endif
#ifndef W_OK
#define W_OK 2
#endif
#endif
static int isanonymous (char *name);
static void ftpserv (int s, void *unused, void *p);
static int pport (struct sockaddr_in *sock, char *arg);
static void ftplogin (struct ftpserv *ftp, char *pass);
static int sendit (struct ftpserv *ftp, const char *command, char *file);
static int recvit (struct ftpserv *ftp, const char *command, char *file);
static void sendmsgfile (int s, int num, char *buf, int size, FILE * fp);
static void SendPasv (int s, struct sockaddr_in *sock);
extern char *addroot (const char *root, const char *name);
extern char *defpath (struct cur_dirs *curdirs, char *path);
/* Command table */
static const char *commands[] =
{
"user",
"acct",
"pass",
"type",
"list",
"cwd",
"dele",
"help",
"quit",
"retr",
"stor",
"port",
"nlst",
"pwd",
"xpwd", /* For compatibility with 4.2BSD */
"mkd ",
"xmkd", /* For compatibility with 4.2BSD */
"xrmd", /* For compatibility with 4.2BSD */
"rmd ",
"stru",
"mode",
"syst",
"xmd5",
"rsme", /* Added by IW0CNB, for resuming interrupted trasnfers */
"rput",
"rnfr",
"rnto",
"cdup",
"appe",
"noop", /* for OS/2 compatibility */
"size",
"pasv", /*PASV mod by G4IDE*/
#ifdef LZW
"xlzw",
#endif
NULLCHAR
};
#if 0
static char challenge[] = "399 PASS challenge : %016lx\n";
static char lowmem[] = "421 System overloaded, try again later\n";
#endif
#ifdef CATALOG
#include "catalog.h"
#define CAT ftpserv_catalog
#define banner __STR(0)
#define banner1 __STR(1)
#define banner2 __STR(2)
#define badcmd __STR(3)
#define binwarn __STR(4)
#define unsupp __STR(5)
#define givepass __STR(6)
#define anonokay __STR(7)
#define logged __STR(8)
#define loggeda __STR(9)
#define typeok __STR(10)
#define only8 __STR(11)
#define deleok __STR(12)
#define mkdok __STR(13)
#define delefail __STR(14)
#define pwdmsg __STR(15)
#define badtype __STR(16)
#define badport __STR(17)
#define unimp __STR(18)
#define bye __STR(19)
#define nodir __STR(20)
#define cantopen __STR(21)
#define sending __STR(22)
#define cantmake __STR(23)
#define writerr __STR(24)
#define portok __STR(25)
#define rxok __STR(26)
#define txok __STR(27)
#define noperm __STR(28)
#define noconn __STR(29)
#define badcheck __STR(30)
#define notlog __STR(31)
#define userfirst __STR(32)
#define okay __STR(33)
#define syst __STR(34)
#define pendingto __STR(35)
#define badseq __STR(36)
#define norename __STR(37)
#define help __STR(38)
#define filesize __STR(39)
#define notaplain __STR(40)
#define nosuchfile __STR(41)
#define pasvmodestr __STR(42)
#else
/* Response messages */
static char banner[] = "220 %s, KA9Q-NOS FTP version %s\n";
static char banner1[] = "230- Ready on %s";
static char banner2[] = "230- Total active FTP sessions at %s: %d out of %d maximum\n";
static char badcmd[] = "500 Unknown command\n";
static char binwarn[] = "150- Warning: type is ASCII and %s appears to be binary\n";
static char unsupp[] = "500 Unsupported command or option\n";
static char givepass[] = "331 Enter PASS command\n";
static char anonokay[] = "331 Anonymous access, give email address as password\n";
static char logged[] = "230 Logged in\n";
static char loggeda[] = "230 Logged in as anonymous, restrictions apply\n";
static char typeok[] = "200 Type %s OK\n";
static char only8[] = "501 Only logical bytesize 8 supported\n";
static char deleok[] = "250 File deleted\n";
static char mkdok[] = "200 MKD ok\n";
static char delefail[] = "550 Delete failed: %s\n";
static char pwdmsg[] = "257 \"%s\" is current directory\n";
static char badtype[] = "501 Unknown type \"%s\"\n";
static char badport[] = "501 Bad port syntax\n";
static char unimp[] = "502 Command not yet implemented\n";
static char bye[] = "221 Goodbye!\n";
static char nodir[] = "553 Can't read directory \"%s\": %s\n";
static char cantopen[] = "550 Can't read file \"%s\": %s\n";
static char sending[] = "150 Opening data connection for %s %s %s\n"; /*N1BEE*/
static char cantmake[] = "553 Can't create \"%s\": %s\n";
static char writerr[] = "552 Write error: %s\n";
static char portok[] = "200 Port command okay\n";
static char rxok[] = "226 File received OK\n";
static char txok[] = "226 File sent OK\n";
static char noperm[] = "550 Permission denied\n";
static char noconn[] = "425 Data connection reset\n";
static char badcheck[] = "425 Bad checksum\n";
static char notlog[] = "530 Please log in with USER and PASS\n";
static char userfirst[] = "503 Login with USER first.\n";
static char okay[] = "200 Ok\n";
static char syst[] = "215 %s Type: L%d Version: %s\n";
static char pendingto[] = "350 Rename awaiting new name.\n";
static char badseq[] = "503 No prior RNFR received - RNTO ignored\n";
static char norename[] = "550 Can't rename: %s\n";
static char help[] = "214-The following commands are recognized.\n";
static char filesize[] = "213 %lu\n";
static char notaplain[] = "550 %s: not a plain file\n";
static char nosuchfile[] = "550 %s: No such file\n";
static char pasvmodestr[] = "227 Entering Passive Mode. %u,%u,%u,%u,%u,%u\n";
#endif
int Sftp = -1; /* Prototype socket for service */
int FtpUsers = 0;
static int FtpMaxUsers = 255;
#ifdef FTPTDISC
static int32 Ftptdiscinit = 0;
/* Set ftp redundancy timer */
int
doftptdisc (int argc, char *argv[], void *p OPTIONAL)
{
return setlong (&Ftptdiscinit, "Ftp redundancy timer (sec)", argc, argv);
}
static void
ftp_redundant (struct ftpserv *ftp)
{
/* Clean up */
(void) shutdown (ftp->control, 2);
close_s (ftp->control);
if (ftp->data != -1) {
(void) shutdown (ftp->data, 2);
close_s (ftp->data);
ftp->data = -1;
}
return;
}
#endif
/* Set ftp Maximum number of connections */
int
doftpmaxclients (int argc, char *argv[], void *p OPTIONAL)
{
return setint (&FtpMaxUsers, "Maximum Ftp incoming clients", argc, argv);
}
/* Start up FTP service */
int
ftpstart (int argc, char *argv[], void *p OPTIONAL)
{
return (installserver (argc, argv, &Sftp, "FTP listener", IPPORT_FTP,
INADDR_ANY, "ftpserv", ftpserv, 2048, NULL));
}
static void
sendmsgfile (int s, int num, char *buf, int size, FILE *fp)
{
while (fgets (buf, size, fp)) {
rip (buf);
usprintf (s, "%d- %s\n", num, buf);
}
}
static int
isanonymous (char *name)
{
FILE *fp, *fpsave = NULLFILE;
int retval = 1;
char buf[128], *cp;
if ((fp = fopen (Userfile, READ_TEXT)) != NULLFILE) {
for ( ; ; ) {
if (fgets (buf, 128, fp) == NULLCHAR) {
if (fpsave) {
(void) fclose (fp);
fp = fpsave;
fpsave = NULLFILE;
continue;
}
break;
}
if (!strnicmp (buf, "#include", 8)) {
rip (buf);
cp = skipwhite (&buf[8]);
fpsave = fp;
if ((fp = fopen (cp, READ_TEXT)) == NULLFILE) {
fp = fpsave;
fpsave = NULLFILE;
}
continue;
}
if ((cp = strpbrk (buf, " \t")) == NULLCHAR)
/* Bogus entry */
continue;
*cp++ = '\0';
if (!stricmp (name, buf)) {
retval = 0;
break; /* Found user */
}
}
if (fpsave)
(void) fclose (fpsave);
(void) fclose (fp);
}
return (retval);
}
static void
ftpserv (
int s, /* Socket with user connection */
void *unused OPTIONAL,
void *p OPTIONAL
) {
struct ftpserv ftp;
char const **cmdp, *cp, *mode = NULLCHAR;
char buf[512], *arg, *file, *cp2;
time_t t;
int cnt, i;
struct sockaddr_in thesocket;
struct cur_dirs dirs;
char *rnfrom = NULLCHAR;
FILE *fpm;
struct sockaddr_in lsocket; /*PASV mod*/
struct sockaddr_in lcsocket;
#if 0
struct stat cwdstat;
char *cp1;
#endif
(void) sockmode (s, SOCK_ASCII);
memset ((char *) &ftp, 0, sizeof (ftp)); /* Start with clear slate */
ftp.data = -1;
(void) sockowner (s, Curproc); /* We own it now */
ftp.control = s;
/* Set default data port */
i = SOCKSIZE;
if (getpeername (s, (char *) &thesocket, &i) != -1) {
thesocket.sin_port = IPPORT_FTPD;
ASSIGN (ftp.port, thesocket);
}
if (++FtpUsers > FtpMaxUsers) {
usprintf (s, "\n200- Sorry, too many FTP users at %s this time. Try again later!\n", Hostname);
FtpUsers--;
return;
}
#ifdef FTPTDISC
/* Set the timeout timer - WG7J */
set_timer (&ftp.tdisc, Ftptdiscinit * 1000L);
ftp.tdisc.func = (void (*)(void *)) ftp_redundant;
ftp.tdisc.arg = &ftp;
start_timer (&ftp.tdisc);
#endif
#ifdef STATS_USE
STATS_adduse (1);
#endif
#ifdef XSERVER
xnotify (X_FTP);
#endif
log (s, "open FTP");
strcpy (buf, ETCdir);
if ((fpm = fopen (strcat (buf, "/banner.ftp"), "r")) != NULL) {
sendmsgfile (s, 220, buf, sizeof (buf), fpm);
(void) fclose (fpm);
}
usprintf (s, banner, Hostname, Version);
(void) init_dirs (&dirs);
ftp.curdirs = &dirs;
(void) time (&t);
cp = ctime (&t);
#if 0
if ((cp1 = strchr (cp, '\n')) != NULLCHAR)
*cp1 = '\0';
#endif
/* Command interpreting loop */
loop:
if ((cnt = recvline (s, (unsigned char *) buf, sizeof (buf))) == -1) {
/* He closed on us */
goto finish;
}
#ifdef FTPTDISC
/* Reset the timeout timer - WG7J */
start_timer (&ftp.tdisc);
#endif
if (cnt == 0) {
/* Can't be a legal FTP command */
usprintf (ftp.control, badcmd);
goto loop;
}
rip (buf);
/* Translate first word to lower case */
for (cp2 = buf; *cp2 != ' ' && *cp2 != '\0'; cp2++)
*cp2 = (char) tolower (*cp2);
/* Find command in table; if not present, return syntax error */
for (cmdp = commands; *cmdp != NULLCHAR; cmdp++)
if (strnicmp (*cmdp, buf, strlen (*cmdp)) == 0)
break;
if (*cmdp == NULLCHAR) {
usprintf (ftp.control, badcmd);
goto loop;
}
#if 0
tcmdprintf ("Command received was: '%s'\n", buf);
#endif
/* Allow only USER, PASS and QUIT before logging in */
if (ftp.path == NULLCHAR) {
switch (cmdp - commands) {
case USER_CMD:
case PASS_CMD:
case QUIT_CMD:
break;
default:
usprintf (ftp.control, notlog);
goto loop;
}
}
arg = &buf[strlen (*cmdp)];
while (*arg == ' ')
arg++;
/* Execute specific command */
switch (cmdp - commands) {
case USER_CMD:
free (ftp.username);
ftp.username = strdup (arg);
#if 0
if(sp_user(ftp.username)) {
time(&ftp.ttim);
usprintf(ftp.control,challenge,ftp.ttim);
} else
#endif
if (isanonymous (arg))
usprintf (ftp.control, anonokay);
else
usprintf (ftp.control, givepass);
break;
case TYPE_CMD:
switch (arg[0]) {
case 'A':
case 'a': /* Ascii */
ftp.type = ASCII_TYPE;
usprintf (ftp.control, typeok, arg);
break;
case 'l':
case 'L':
while (*arg != ' ' && *arg != '\0')
arg++;
if (*arg == '\0' || *++arg != '8') {
usprintf (ftp.control, only8);
break;
}
ftp.type = LOGICAL_TYPE;
ftp.logbsize = 8;
usprintf (ftp.control, typeok, arg);
break;
case 'B':
case 'b': /* Binary */
case 'I':
case 'i': /* Image */
ftp.type = IMAGE_TYPE;
usprintf (ftp.control, typeok, arg);
break;
default: /* Invalid */
usprintf (ftp.control, badtype, arg);
break;
}
break;
case QUIT_CMD:
usprintf (ftp.control, bye);
goto finish;
case RNFR_CMD:
file = addroot (ftp.curdirs->dir, arg);
if (!permcheck (ftp.path, ftp.perms, RNFR_CMD, file))
usprintf (ftp.control, noperm);
else if (access (file, 6))
usprintf (ftp.control, cantopen, file, SYS_ERRLIST(errno));
else {
usprintf (ftp.control, pendingto);
rnfrom = strdup (file);
}
free (file);
break;
case RNTO_CMD:
file = addroot (ftp.curdirs->dir, arg);
if (rnfrom == NULLCHAR)
usprintf (ftp.control, badseq);
else {
if (!permcheck (ftp.path, ftp.perms, RNTO_CMD, file))
usprintf (ftp.control, noperm);
else {
if (rename (rnfrom, file) == -1)
usprintf (ftp.control, norename, SYS_ERRLIST(errno));
else
usprintf (ftp.control, okay);
}
free (rnfrom);
rnfrom = NULLCHAR;
}
free (file);
break;
case RETR_CMD:
case RSME_CMD:
file = addroot (ftp.curdirs->dir, arg);
switch (ftp.type) {
case IMAGE_TYPE:
case LOGICAL_TYPE:
mode = READ_BINARY;
break;
default:
case ASCII_TYPE:
mode = READ_TEXT;
break;
}
if (!permcheck (ftp.path, ftp.perms, RETR_CMD, file))
usprintf (ftp.control, noperm);
#ifdef UNIX
else if (!ftpsecuritycheck (file, ftp.perms, R_OK) || (ftp.fp = fopen (file, mode)) == NULLFILE)
#else
else if ((ftp.fp = fopen (file, mode)) == NULLFILE)
#endif
usprintf (ftp.control, cantopen, file, SYS_ERRLIST(errno));
else {
if ((cmdp - commands) == RSME_CMD) {
log (ftp.control, "RSME %s", file);
if (ftp.type == ASCII_TYPE && isbinary (ftp.fp))
usprintf (ftp.control, binwarn, file);
(void) sendit (&ftp, "RSME", file);
} else {
log (ftp.control, "RETR %s", file);
if (ftp.type == ASCII_TYPE && isbinary (ftp.fp))
usprintf (ftp.control, binwarn, file);
(void) sendit (&ftp, "RETR", file);
}
}
free (file);
break;
case SIZE_CMD:
file = addroot (ftp.curdirs->dir, arg);
if (!permcheck (ftp.path, ftp.perms, RETR_CMD, file))
usputs (ftp.control, noperm);
else if ((ftp.fp = fopen (file, READ_BINARY)) != NULLFILE) {
usprintf (ftp.control, filesize, filelength (fileno (ftp.fp)));
(void) fclose (ftp.fp);
} else if (!access (file, 0))
usprintf (ftp.control, notaplain, file);
else
usprintf (ftp.control, nosuchfile, file);
free (file);
break;
case STOR_CMD:
cp = "STOR";
goto store2;
case APPE_CMD:
cp = "APPE";
goto store2;
case RPUT_CMD:
cp = "RPUT";
store2:
file = addroot (ftp.curdirs->dir, arg);
switch (ftp.type) {
case IMAGE_TYPE:
case LOGICAL_TYPE:
if (cmdp - commands != STOR_CMD)
mode = APPEND_BINARY;
else
mode = WRITE_BINARY;
break;
default:
case ASCII_TYPE:
if (cmdp - commands != STOR_CMD)
mode = APPEND_TEXT;
else
mode = WRITE_TEXT;
break;
}
if (!permcheck (ftp.path, ftp.perms, cmdp - commands, file))
usprintf (ftp.control, noperm);
#ifdef UNIX
else if (!ftpsecuritycheck (file, ftp.perms, W_OK) || (ftp.fp = fopen (file, mode)) == NULLFILE)
#else
else if ((ftp.fp = fopen (file, mode)) == NULLFILE)
#endif
usprintf (ftp.control, cantmake, file, SYS_ERRLIST(errno));
else {
log (ftp.control, "%s %s", cp, file);
(void) recvit (&ftp, cp, file);
#ifdef UNIX
if (CREATEsecure) {
(void) chown (file, (uid_t) CREATEuid, (gid_t) CREATEgid);
(void) chmod (file, (mode_t) CREATEmask);
}
#endif
}
free (file);
break;
case PORT_CMD:
if (pport (&ftp.port, arg) == -1)
usprintf (ftp.control, badport);
else
usprintf (ftp.control, portok);
break;
#ifndef CPM
case LIST_CMD:
case NLST_CMD:
file = addroot (ftp.curdirs->dir, defpath (ftp.curdirs, arg));
if (!permcheck (ftp.path, ftp.perms, RETR_CMD, file))
usprintf (ftp.control, noperm);
else {
#ifdef UNIX
if (ftpsecuritycheck (file, ftp.perms, R_OK)) {
#endif
cp = Command->curdirs->dir;
Command->curdirs->dir = ftp.curdirs->dir;
if ((ftp.fp = dir (file, ((cmdp - commands) == LIST_CMD) ? 2 : 0)) == NULLFILE)
usprintf (ftp.control, nodir, file, SYS_ERRLIST(errno));
else
(void) sendit (&ftp, ((cmdp - commands) == LIST_CMD) ? "LIST" : "NLST", file);
Command->curdirs->dir = cp;
#ifdef UNIX
} else
usprintf (ftp.control, noperm);
#endif
}
free (file);
break;
case CDUP_CMD:
sprintf (arg, ".."); /* and fall through */
case CWD_CMD:
#ifdef old_CALLSERVER
/* if the requested path contains the CROM drive letter: */
if (CDROM != NULLCHAR && strnicmp (CDROM, arg, 2) == 0) {
if (strchr (arg, '/') == NULLCHAR) {
file = (char *) mallocw (strlen (arg) + 2);
sprintf (file, "%s/", arg);
} else
file = strdup (arg);
if (!permcheck (ftp.path, ftp.perms, RETR_CMD, file)) {
usprintf (ftp.control, noperm);
free (file);
#ifdef MSDOS
/* Don'tcha just LOVE %%$#@!! MS-DOS? - which is what we are running */
} else if (file[2] == '/' || access (file, 0) == 0) {
#else
} else if (access (file, 0) == 0) { /* See if it exists */
#endif
/* Succeeded, record in control block */
free (ftp.cd);
ftp.cd = file;
usprintf (ftp.control, "You may return to your default drive & directory by entering:\n\t\t\"cd %s\"\n\n", ftp.path);
usprintf (ftp.control, pwdmsg, file);
} else {
/* Failed, don't change anything */
usprintf (ftp.control, nodir, file, SYS_ERRLIST(errno));
free (file);
}
break;
}
/* requested path does not contain CDROM drive letter: */
/* if current dir is in CDROM - and a "off-root" is requested:
go back to default path. */
if ((CDROM != NULLCHAR && strnicmp (ftp.cd, CDROM, 2) == 0) && (arg[0] == '/')) {
free (ftp.cd);
ftp.cd = strdup (ftp.path); /* go back to default path */
}
#endif /* #ifdef CALLSERVER */
if (*arg == '/' || *arg == '\\')
file = addroot (ftp.path, &arg[1]);
else
file = addroot (ftp.curdirs->dir, arg);
if (!permcheck (ftp.path, ftp.perms, RETR_CMD, file))
usprintf (ftp.control, noperm);
else if (dir_ok (file, ftp.curdirs)) {
/* Succeeded */
/* If exists, send the contents of 'desc.ftp' in the new
* directory...
*/
strncpy (buf, file, 512);
if ((fpm = fopen (strcat (buf, "/desc.ftp"), "r")) != NULL) {
sendmsgfile (ftp.control, 257, buf, sizeof (buf), fpm);
(void) fclose (fpm);
}
#ifndef MSDOS
/* If exists, send the contents of '.message' in the new
* directory...
*/
strncpy (buf, file, 512);
if ((fpm = fopen (strcat (buf, "/.message"), "r")) != NULL) {
sendmsgfile (ftp.control, 257, buf, sizeof (buf), fpm);
(void) fclose (fpm);
}
#endif
usprintf (ftp.control, pwdmsg,
(!strcmp (ftp.root, ftp.curdirs->dir)) ? "/"
: (!strncmp (ftp.root, ftp.curdirs->dir, strlen (ftp.root)))
? &ftp.curdirs->dir[strlen (ftp.root)]
: ftp.curdirs->dir);
} else
/* Failed, nothing changed */
usprintf (ftp.control, nodir, arg, SYS_ERRLIST(errno));
free (file);
break;
case XPWD_CMD:
case PWD_CMD:
usprintf (ftp.control, pwdmsg,
(!strcmp (ftp.root, ftp.curdirs->dir)) ? "/"
: (!strncmp (ftp.root, ftp.curdirs->dir, strlen (ftp.root)))
? &ftp.curdirs->dir[strlen (ftp.root)]
: ftp.curdirs->dir);
break;
#else
case LIST_CMD:
case NLST_CMD:
case CWD_CMD:
case XPWD_CMD:
case PWD_CMD:
#endif
case ACCT_CMD:
usprintf (ftp.control, unimp);
break;
case HELP_CMD:
usputs (ftp.control, help);
for (cmdp = commands, i = buf[0] = 0; *cmdp != NULLCHAR; cmdp++) {
strcat (buf, " ");
strcat (buf, *cmdp);
if (strlen (*cmdp) == 3)
strcat (buf, " ");
if (i++ == 9) {
(void) strupr (buf);
usprintf (ftp.control, "%s\n", buf);
i = buf[0] = 0;
}
}
if (i) {
(void) strupr (buf);
usprintf (ftp.control, "%s\n", buf);
}
usprintf (ftp.control, "214 Report problems to sysop@%s\n", Hostname);
break;
case NOOP_CMD:
usputs (ftp.control, okay);
break;
case DELE_CMD:
file = addroot (ftp.curdirs->dir, arg);
if (!permcheck (ftp.path, ftp.perms, DELE_CMD, file))
usprintf (ftp.control, noperm);
else if (unlink (file) == 0) {
log (ftp.control, "DELE %s", file);
usprintf (ftp.control, deleok);
} else
usprintf (ftp.control, delefail, SYS_ERRLIST(errno));
free (file);
break;
case PASS_CMD:
if (ftp.username == NULLCHAR)
usprintf (ftp.control, userfirst);
else
ftplogin (&ftp, arg);
break;
#ifndef CPM
case XMKD_CMD:
case MKD_CMD:
file = addroot (ftp.curdirs->dir, arg);
if (!permcheck (ftp.path, ftp.perms, MKD_CMD, file))
usprintf (ftp.control, noperm);
else if (mkdir (file, (mode_t) 0777) == 0) {
log (ftp.control, "MKD %s", file);
usprintf (ftp.control, mkdok);
} else
usprintf (ftp.control, cantmake, file, SYS_ERRLIST(errno));
free (file);
break;
case XRMD_CMD:
case RMD_CMD:
file = addroot (ftp.curdirs->dir, arg);
if (!permcheck (ftp.path, ftp.perms, RMD_CMD, file))
usprintf (ftp.control, noperm);
else if (rmdir (file) == 0) {
log (ftp.control, "RMD %s", file);
usprintf (ftp.control, deleok);
} else
usprintf (ftp.control, delefail, SYS_ERRLIST(errno));
free (file);
break;
case STRU_CMD:
if (tolower (arg[0]) != 'f')
usprintf (ftp.control, unsupp);
else
usprintf (ftp.control, okay);
break;
case MODE_CMD:
if (tolower (arg[0]) != 's')
usprintf (ftp.control, unsupp);
else
usprintf (ftp.control, okay);
break;
case SYST_CMD:
usprintf (ftp.control, syst, System, NBBY, Version);
break;
case PASV_CMD: /*PASV mod*/
/* Send the PASV message. Use the IP address
* on the local end of our control connection. */
if (ftp.data != -1) /* left over error - kill the socket first */
close_s (ftp.data);
ftp.data = socket (AF_INET, SOCK_STREAM, 0);
(void) listen (ftp.data, 0);
i = SOCKSIZE;
(void) getsockname (ftp.data, (char *) &lsocket, &i);
if (!i)
break;
i = SOCKSIZE;
(void) getsockname (ftp.control, (char *) &lcsocket, &i);
if (!i)
break;
lsocket.sin_addr.s_addr = lcsocket.sin_addr.s_addr;
/* send the address to the client. */
SendPasv (ftp.control, &lsocket);
break;
case XMD5_CMD: /*PASV mod*/
file = addroot (ftp.curdirs->dir, arg);
switch (ftp.type) {
case IMAGE_TYPE:
case LOGICAL_TYPE:
mode = READ_BINARY;
break;
default:
case ASCII_TYPE:
mode = READ_TEXT;
break;
}
if (!permcheck (ftp.path, ftp.perms, RETR_CMD, file))
usprintf (ftp.control, noperm);
#ifdef UNIX
else if (!ftpsecuritycheck (file, ftp.perms, R_OK) || (ftp.fp = fopen (file, mode)) == NULLFILE)
#else
else if ((ftp.fp = fopen (file, mode)) == NULLFILE)
#endif
usprintf (ftp.control, cantopen, file, SYS_ERRLIST(errno));
else {
char hash[16];
log (ftp.control, "XMD5 %s", file);
if (ftp.type == ASCII_TYPE && isbinary (ftp.fp))
usprintf (ftp.control, binwarn, file);
(void) md5hash (ftp.fp, hash, ftp.type == ASCII_TYPE);
(void) fclose (ftp.fp);
ftp.fp = NULLFILE;
usprintf (ftp.control, "200 ");
for (i = 0; i < 16; i++)
usprintf (ftp.control, "%02x", hash[i] & 0xff);
usprintf (ftp.control, " %s\n", file);
}
free (file);
break;
#ifdef LZW
case XLZW_CMD:
if (ftp.lzw)
usprintf (ftp.control, "550 Already using LZW compression\n");
else {
usprintf (ftp.control, okay);
sscanf (&buf[5], "%d %d", &ftp.lzwbits, &ftp.lzwmode);
ftp.lzw = 1;
}
break;
default:
break;
#endif
}
#endif
goto loop;
finish:
#ifdef FTPTDISC
stop_timer (&ftp.tdisc);
#endif
log (ftp.control, "close FTP from '%s'", ftp.username);
FtpUsers--;
#ifdef XSERVER
xnotify (X_FTP);
#endif
/* Clean up */
close_s (ftp.control);
if (ftp.data != -1)
close_s (ftp.data);
if (ftp.fp != NULLFILE)
(void) fclose (ftp.fp);
free (ftp.username);
free (ftp.path);
free_dirs (&dirs);
free (rnfrom);
}
/* Shut down FTP server */
int
ftp0 (int argc OPTIONAL, char *argv[] OPTIONAL, void *p OPTIONAL)
{
return (deleteserver (&Sftp));
}
static
int
pport (struct sockaddr_in *sock, char *arg)
{
uint32 n;
int i;
n = 0;
for (i = 0; i < 4; i++) {
n = (uint32) atoi (arg) + (n << 8);
if ((arg = strchr (arg, ',')) == NULLCHAR)
return -1;
arg++;
}
sock->sin_addr.s_addr = n;
n = (uint32) atoi (arg);
if ((arg = strchr (arg, ',')) == NULLCHAR)
return -1;
arg++;
n = (uint32) atoi (arg) + (n << 8);
sock->sin_port = (int16) n;
return 0;
}
/* Attempt to log in the user whose name is in ftp->username and password
* in pass
*/
static void
ftplogin (struct ftpserv *ftp, char *pass)
{
char *path, buf[128], *cp;
char *p, *cp1;
time_t t;
FILE *fp;
int anony = 0;
path = mallocw (200);
if ((ftp->perms = userlogin (ftp->username, pass, &path, 200, &anony)) == -1) {
log (ftp->control, "FTP login refused - '%s'", ftp->username);
usprintf (ftp->control, noperm);
free (path);
return;
}
/* Set up current directory and path prefix */
ftp->path = strdup (path);
cp = strdup (path);
if ((cp1 = strchr (cp, ';')) != NULLCHAR)
*cp1 = '\0';
if ((cp1 = strchr (cp, '=')) != NULLCHAR)
*cp1 = '\0';
#ifdef MSDOS
if (*cp == '/' || *cp == '\\') {
sprintf (buf, "%c:%s", ftp->curdirs->drv + '`', cp);
ftp->root = strdup (buf);
} else
#endif
ftp->root = strdup (cp); /*lint !e539 */
free (cp);
{
FILE *out;
sprintf (buf, "%s/ftp.log", LOGdir);
if ((out = fopen (buf, APPEND_TEXT)) != NULLFILE) {
time_t tt;
(void) time (&tt);
fprintf (out, "FTP from %s (%s) on %s", ftp->username, pass, ptime (&tt));
(void) fclose (out);
}
}
(void) time (&t);
cp = ctime (&t);
usprintf (ftp->control, banner1, cp);
usprintf (ftp->control, banner2, Hostname, FtpUsers, FtpMaxUsers);
/* everyone gets the ftpmotd file, if it exists */
if ((fp = fopen (Ftpmotd, "r")) != NULL) {
sendmsgfile (ftp->control, 230, buf, sizeof (buf), fp);
(void) fclose (fp);
}
/* if there is a message.ftp file in the login dir, send it too */
sprintf (buf, "%s%s%s", path, (path[strlen (path) - 1] == '/') ? "" : "/", "message.ftp");
if ((fp = fopen (buf, "r")) != NULL) {
sendmsgfile (ftp->control, 230, buf, sizeof (buf), fp);
(void) fclose (fp);
}
path = strdup (ftp->path);
if ((p = strpbrk (path, "=;")) != NULLCHAR)
*p = 0;
if (dir_ok (path, ftp->curdirs)) {
/* Succeeded */
/* If exists, send the contents of 'desc.ftp' in the new directory... */
sprintf (buf, "%s/desc.ftp", path);
if ((fp = fopen (buf, "r")) != NULL) {
sendmsgfile (ftp->control, 230, buf, sizeof (buf), fp);
(void) fclose (fp);
}
}
free (path);
if (!anony) {
usprintf (ftp->control, logged);
log (ftp->control, "FTP login - '%s'", ftp->username);
} else {
usprintf (ftp->control, loggeda);
log (ftp->control, "FTP anonymous login - '%s' (%s)", ftp->username, pass);
}
usflush (ftp->control);
kwait (NULL);
}
#ifdef MSDOS
/* Illegal characters in a DOS filename */
static char badchars[] = "\"[]|<>+=;,";
#endif
/* Return 1 if the file operation is allowed, 0 otherwise */
int
permcheck (char *path, long perms, int op, char *file)
{
char *cp, *cp1;
int newperms;
int match = FALSE;
int notabove = FALSE;
if (file == NULLCHAR || path == NULLCHAR)
return 0; /* Probably hasn't logged in yet */
/* To get to the CDROM - EVERYBODY gets read privs, regardless of what
/ftpusers has to say about it!!! - kb7yw */
#ifdef CALLSERVER
if (CDROM != NULLCHAR && strnicmp (file, CDROM, 2) == 0) {
/* Check for characters illegal in MS-DOS file names */
for (cp = badchars; *cp != '\0'; cp++) {
if (strchr (&file[2], *cp) != NULLCHAR)
return 0;
}
switch (op) { /* What to do when the user is on the cd-rom drive */
case RETR_CMD: /* Everybody gets read privs regardless of ftpusers */
/* User has permission to read files */
return 1;
case DELE_CMD:
case RMD_CMD:
/* User must not have permission to (over)write files */
case STOR_CMD:
case MKD_CMD:
/* User must NOT have permission to (over)write files */
return 0;
} /* switch(op) */
} /* if strncmp(.... */
# endif /* #ifdef CALLSERVER */
#ifndef MAC
/* The target file must be under the user's allowed search path */
/* We let them specify multiple paths using path;path... -russ */
/* Allow /nos/public;/nos/public/incoming=3 style path statements -bruce */
for (cp = path; *cp != '\0'; cp = cp1 + 1) {
char *cp2;
newperms = perms;
if ((cp1 = strchr (cp, ';')) == NULLCHAR)
cp1 = &cp[strlen(cp) - 1];
cp2 = strchr (cp, '=');
if (!cp2 || (cp2 > cp1))
cp2 = cp1;
else
newperms = atoi (cp2 + 1);
/* Take care of the case when we have a path statement in ftpusers
like: /nos/public;/nos/public/incoming=3 and the user cwd's to
/nos. Make sure the user is not above the smallest length path
so that we can have path statements like: /nos/public;/nos=1;/nos...
*/
if ((int) strlen (file) >= (cp2 - cp))
notabove = TRUE;
if (!strnicmp (file, cp, (unsigned) (cp2 - cp))
#ifdef MSDOS
|| !strnicmp (file + 2, cp, cp2 - cp)
#endif
) {
match = TRUE;
perms = newperms;
}
}
/* We must have both a match and not be above the smallest level to
continue */
if (!match || !notabove)
return 0;
#endif
#ifdef MSDOS
/* Check for characters illegal in MS-DOS file names */
for (cp = badchars; *cp != '\0'; cp++) {
if (strchr (file, *cp) != NULLCHAR)
return 0;
}
#endif
switch (op) {
case RETR_CMD:
/* User must have permission to read files */
if (perms & FTP_READ)
return 1;
return 0;
case DELE_CMD:
case RMD_CMD:
case RPUT_CMD:
case APPE_CMD:
/* User must have permission to (over)write files */
if (perms & FTP_WRITE)
return 1;
return 0;
case RNFR_CMD:
case RNTO_CMD:
case STOR_CMD:
case MKD_CMD:
/* User must have permission to (over)write files, or permission
* to create them if the file doesn't already exist
*/
if (perms & FTP_WRITE)
return 1;
if (access (file, 2) == -1 && (perms & FTP_CREATE))
return 1;
return 0;
default:
break;
}
return 0; /* "can't happen" -- keep lint happy */
}
static int
sendit (struct ftpserv *ftp, const char *command, char *file)
{
long total, starting;
unsigned long check;
struct sockaddr_in dport;
char *cp, *cp2;
char fsizetext[20]; /* N1BEE */
int pasv = 0;
if (ftp->data != -1)
pasv = 1;
fsizetext[0] = 0;
if (!pasv) {
ftp->data = socket (AF_INET, SOCK_STREAM, 0);
dport.sin_family = AF_INET;
dport.sin_addr.s_addr = INADDR_ANY;
dport.sin_port = IPPORT_FTPD;
(void) bind (ftp->data, (char *) &dport, SOCKSIZE);
}
sprintf (fsizetext, "(%lu bytes)", filelength (fileno (ftp->fp)));
usprintf (ftp->control, sending, command, (strlen (file) == strlen (ftp->path)) ? "/" : &file[strlen (ftp->path)], fsizetext); /* N1BEE */
if (!pasv) {
if (connect (ftp->data, (char *) &ftp->port, SOCKSIZE) == -1) {
(void) fclose (ftp->fp);
ftp->fp = NULLFILE;
close_s (ftp->data);
ftp->data = -1;
usprintf (ftp->control, noconn);
return -1;
}
} else { /* PASV mode */
/* wait for the client to open the connection */
/* ftp->data has been setup already */
(void) accept (ftp->data, NULLCHAR, (int *) NULL);
}
if (strcmp (command, "RSME") == 0) {
total = -1;
cp = mallocw (40);
if (recvline (ftp->control, (unsigned char *) cp, 40) == -1) {
free (cp);
goto send_err;
}
starting = atol (cp);
/* If checksum field is not present go on anyway, for compatibility
* with previous scheme. If present check it and barf if wrong.
*/
cp2 = strchr (cp, ' ');
if (cp2 != NULLCHAR) {
check = (unsigned long) atol (cp2);
check -= checksum (ftp->fp, starting);
if (check != 0) {
free (cp);
usprintf (ftp->control, badcheck);
(void) shutdown (ftp->data, 1); /* Blow away data connection */
goto send_err;
}
} else if (fseek (ftp->fp, starting, SEEK_SET) != 0) {
free (cp);
usprintf (ftp->control, noconn);
(void) shutdown (ftp->data, 2); /* Blow away data connection */
goto send_err;
}
}
#ifdef FTPTDISC
/* Turn off the timeout timer here, some ftp's could
* take a long time with sloooow packet channels - WG7J
*/
stop_timer (&ftp->tdisc);
#endif
#ifdef LZW
if (ftp->lzw)
lzwinit (ftp->data, ftp->lzwbits, ftp->lzwmode);
#endif
/* Do the actual transfer */
total = sendfile (ftp->fp, ftp->data, ftp->type, 0);
#ifdef FTPTDISC
/* And turn it back on now */
start_timer (&ftp->tdisc);
#endif
if (total == -1) {
/* An error occurred on the data connection */
usprintf (ftp->control, noconn);
(void) shutdown (ftp->data, 2); /* Blow away data connection */
} else
usprintf (ftp->control, txok);
send_err:
(void) fclose (ftp->fp);
ftp->fp = NULLFILE;
close_s (ftp->data);
ftp->data = -1;
#ifdef LZW
ftp->lzw = 0;
#endif
if (total == -1)
return -1;
else
return 0;
}
static int
recvit (struct ftpserv *ftp, const char *command, char *file)
{
struct sockaddr_in dport;
long total, starting;
int pasv = 0;
if (ftp->data != -1)
pasv = 1;
if (!pasv) {
ftp->data = socket (AF_INET, SOCK_STREAM, 0);
dport.sin_family = AF_INET;
dport.sin_addr.s_addr = INADDR_ANY;
dport.sin_port = IPPORT_FTPD;
(void) bind (ftp->data, (char *) &dport, SOCKSIZE);
}
usprintf (ftp->control, sending, command, file, "");
if (!pasv) {
if (connect (ftp->data, (char *) &ftp->port, SOCKSIZE) == -1) {
(void) fclose (ftp->fp);
ftp->fp = NULLFILE;
close_s (ftp->data);
ftp->data = -1;
usprintf (ftp->control, noconn);
return -1;
}
} else { /* PASV mode */
/* wait for the client to open the connection */
/* ftp->data has been setup already */
(void) accept (ftp->data, NULLCHAR, (int *) NULL);
}
if (strcmp (command, "APPE") == 0)
fseek (ftp->fp, 0, SEEK_END);
if (strcmp (command, "RPUT") == 0) {
if ((starting = (long) getsize (ftp->fp)) == -1)
starting = 0L;
usprintf (ftp->control, "%lu %lu\n", starting, checksum (ftp->fp, starting));
fseek (ftp->fp, starting, SEEK_SET);
}
#ifdef FTPTDISC
/* Turn of the timeout timer here; some ftp's could
* take a long time with sloooow packet channels - WG7J
*/
stop_timer (&ftp->tdisc);
#endif
#ifdef LZW
if (ftp->lzw)
lzwinit (ftp->data, ftp->lzwbits, ftp->lzwmode);
#endif
/* Do the actual transfer */
total = recvfile (ftp->fp, ftp->data, ftp->type, 0);
#ifdef FTPTDISC
/* And turn it back on now */
start_timer (&ftp->tdisc);
#endif
#ifdef CPM
if (ftp->type == ASCII_TYPE)
putc (CTLZ, ftp->fp);
#endif
if (total == -1) {
/* An error occurred while writing the file */
usprintf (ftp->control, writerr, SYS_ERRLIST(errno));
(void) shutdown (ftp->data, 2); /* Blow it away */
} else
usprintf (ftp->control, rxok);
close_s (ftp->data);
ftp->data = -1;
(void) fclose (ftp->fp);
ftp->fp = NULLFILE;
#ifdef LZW
ftp->lzw = 0;
#endif
if (total == -1)
return -1;
else
return 0;
}
#ifdef UNIX
#if 0
extern uid_t geteuid (void);
extern gid_t getegid (void);
#endif
int
ftpsecuritycheck (char *filename, int perms, int mode)
{
int accval; /*lint -esym(550, accval) */
int retval;
int fd;
char *buf, *cp;
uid_t ruid = getuid ();
gid_t rgid = getgid ();
#if 0
uid_t euid = geteuid ();
gid_t egid = getegid ();
#endif
if (perms & SYSOP_CMD)
return (1);
#if 0
log (-1, "ftpsecuritycheck: file - %s", filename);
log (-1, "ftpsecuritycheck: mode - %d", mode);
log (-1, "ftpsecuritycheck: ftpgid - %d", ACCESSgid);
log (-1, "ftpsecuritycheck: ftpuid - %d", ACCESSuid);
log (-1, "ftpsecuritycheck: ruid - %d", ruid);
log (-1, "ftpsecuritycheck: rgid - %d", rgid);
log (-1, "ftpsecuritycheck: euid - %d", euid);
log (-1, "ftpsecuritycheck: egid - %d", egid);
#endif
(void) setregid ((int16) ACCESSgid, (unsigned short) -1);
(void) setreuid ((int16) ACCESSuid, (unsigned short) -1);
#if 0
log (-1, "ftpsecuritycheck: uid - %d", getuid ());
log (-1, "ftpsecuritycheck: gid - %d", getgid ());
log (-1, "ftpsecuritycheck: euid - %d", geteuid ());
log (-1, "ftpsecuritycheck: egid - %d", getegid ());
#endif
retval = ((accval = access (filename, mode)) == 0);
/* for write permissions, you can't JUST use access, if the
file is a new file being created. */
if (mode == W_OK && errno == 2) {
/* if the file doesn't already exist, it takes two steps */
if (access (filename, 0)) {
/* first we see if the file CAN be created */
fd = open (filename, O_CREAT | O_WRONLY | O_TRUNC);
if (fd != -1) {
close (fd);
unlink (filename);
retval = 1;
}
}
}
/* now we PROBABLY know whether we can do this, but we must also
check the directory, to see if we have write permissions, if it
is a write
*/
if (mode == W_OK) {
buf = strdup (filename);
cp = strrchr (buf, '/');
if (cp)
*cp = 0;
else
strcpy (buf, ".");
/* set retval to whether we have write permissions to the directory */
if (access (buf, mode))
retval = 0;
free (buf);
}
(void) setreuid (ruid, (unsigned short) -1);
(void) setregid (rgid, (unsigned short) -1);
#if 0
log (-1, "ftpsecuritycheck: neweuid - %d", geteuid ());
log (-1, "ftpsecuritycheck: newegid - %d", getegid ());
log (-1, "ftpsecuritycheck: newuid - %d", getuid ());
log (-1, "ftpsecuritycheck: newgid - %d", getgid ());
log (-1, "ftpsecuritycheck: accval = %d", accval);
log (-1, "ftpsecuritycheck: retval = %d", retval);
log (-1, "ftpsecuritycheck: errno = %d", errno);
#endif
if (!retval)
errno = EACCES;
return (retval);
}
#endif
/* PASV mod */
static void
SendPasv (int s, struct sockaddr_in *sock)
{
/* Send PORT a,a,a,a,p,p message */
usprintf (s, pasvmodestr,
hibyte (hiword (sock->sin_addr.s_addr)),
lobyte (hiword (sock->sin_addr.s_addr)),
hibyte (loword (sock->sin_addr.s_addr)),
lobyte (loword (sock->sin_addr.s_addr)),
hibyte (sock->sin_port),
lobyte (sock->sin_port));
}